package org.wrigut.parser;
import java.util.ArrayList;

import org.wrigut.scanlib.*;

/* Parser.java
 * 
 * Project 1 - CS 3361
 * 
 * Authors: Chris Gutierrez
 *          Jordan Wright
 * 
 * This class takes in as input an arraylist of Tokens (generated previously by the Scanner) and applies the CFG to determine
 * whether or not the inputted program is valid or not. If the program is valid, a parse tree for the program is generated, and 
 * if not, and ParseException is returned.
 */

public class Parser {
	//Attributes
	private ArrayList <Token> tokens;
	private ParseTree root;
	
	//Constructor
	public Parser(ArrayList <Token> t) throws ParseException
	{
		tokens = t;
		//Here we goooooooo!!!
		root = program();
	}	
		
	private ParseTree program() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree program;
		if(tok.getType()==TokenType.id || tok.getType()==TokenType.read || tok.getType()==TokenType.write || tok.getType()==TokenType.EOF)
		{
			program = new ParseTree("program",1);
			program.setBranch(0,stmt_list());
			match(TokenType.EOF);
		}
		else
		{
			program = null;
			throw new ParseException("Not a valid program");
		}
		return program;
	}
	
	private ParseTree stmt_list() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree stmt_list;
		if(tok.getType()==TokenType.id || tok.getType()==TokenType.read || tok.getType()==TokenType.write)
		{
			stmt_list = new ParseTree("stmt_list",2);
			stmt_list.setBranch(0,stmt());
			stmt_list.setBranch(1, stmt_list());
		}
		else if (tok.getType() == TokenType.EOF)
		{
			stmt_list = new ParseTree("stmt_list");
		}
		else
		{
			throw new ParseException("More statements expected");
		}
		return stmt_list;
	}
	
	private ParseTree stmt() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree stmt;
		if(tok.getType()==TokenType.id)
		{
			stmt = new ParseTree("stmt",3);
			stmt.setLeaf(0,match(TokenType.id));
			tok = tokens.get(0);
			if(tok.getType()==TokenType.assign)
			{
				stmt.setLeaf(1,match(TokenType.assign));
				stmt.setBranch(2, expr());
			}
			else
			{
				throw new ParseException("Assignment expected");
			}
		}
		else if(tok.getType() == TokenType.read)
		{
			stmt = new ParseTree("stmt", 2);
			stmt.setLeaf(0, match(TokenType.read));
			stmt.setLeaf(1, match(TokenType.id));
		}
		else if(tok.getType() == TokenType.write)
		{
			stmt = new ParseTree("stmt", 2);
			stmt.setLeaf(0, match(TokenType.write));
			stmt.setBranch(1, expr());
		}
		else
		{
			throw new ParseException("Statement expected");
		}
		return stmt;
	}
	
	private ParseTree expr() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree expr;
		if(tok.getType()==TokenType.id || tok.getType()==TokenType.number || tok.getType()==TokenType.lparen)
		{
			expr = new ParseTree("expr",2);
			expr.setBranch(0, term());
			expr.setBranch(1, term_tail());
		}
		else
		{
			throw new ParseException("Expression expected");
		}
		return expr;
	}
	
	private ParseTree term_tail() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree term_tail;
		TokenType t = tok.getType();
		if(t == TokenType.plus || t == TokenType.minus)
		{
			term_tail = new ParseTree("term_tail",3);
			term_tail.setBranch(0, add_op());
			term_tail.setBranch(1, term());
			term_tail.setBranch(2, term_tail());
		}
		else if(t == TokenType.rparen || t == TokenType.id || t == TokenType.read || t == TokenType.write || t == TokenType.EOF)
		{
			term_tail = new ParseTree("term_tail");
		}
		else
		{
			throw new ParseException("Another term expected");
		}
		return term_tail;
	}
	
	private ParseTree term() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree term;
		if(tok.getType() == TokenType.id || tok.getType() == TokenType.number || tok.getType() == TokenType.lparen)
		{
			term = new ParseTree("term",2);
			term.setBranch(0,factor());
			term.setBranch(1, factor_tail());
		}
		else
		{
			throw new ParseException("Term expected");
		}
		return term;
	}
	
	private ParseTree factor_tail() throws ParseException
	{
		Token tok = tokens.get(0);
		TokenType t = tok.getType();
		ParseTree factor_tail;
		if(tok.getType() == TokenType.times || tok.getType() == TokenType.div)
		{
			factor_tail = new ParseTree("factor_tail",3);
			factor_tail.setBranch(0, mult_op());
			factor_tail.setBranch(1, factor());
			factor_tail.setBranch(2, factor_tail());
		}
		else if (t == TokenType.plus || t == TokenType.minus || t == TokenType.rparen || t == TokenType.id || t == TokenType.read || t == TokenType.write || t == TokenType.EOF)
		{
			factor_tail = new ParseTree("factor_tail");
		}
		else
		{
			throw new ParseException("Another factor expected");
		}
		return factor_tail;
	}
	
	private ParseTree factor() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree factor;
		if (tok.getType() == TokenType.id)
		{
			factor = new ParseTree("factor",1);
			factor.setLeaf(0, match(TokenType.id));
		}
		else if(tok.getType() == TokenType.number)
		{
			factor = new ParseTree("factor",1);
			factor.setLeaf(0, match(TokenType.number));
		}
		else if(tok.getType() == TokenType.lparen)
		{
			factor = new ParseTree("factor",3);
			factor.setLeaf(0, match(TokenType.lparen));
			factor.setBranch(1, expr());
			tok = tokens.get(0);
			if(tok.getType() == TokenType.rparen)
			{
			factor.setLeaf(2, match(TokenType.rparen));
			}
			else
			{
				throw new ParseException("Right Parenthesis Expected.");
			}
		}
		else
		{
			throw new ParseException("Factor expected");
		}
		return factor;
	}
	
	private ParseTree add_op() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree add_op;
		if(tok.getType() == TokenType.plus)
		{
			add_op = new ParseTree("add_op",1);
			add_op.setLeaf(0, match(TokenType.plus));
		}
		else if(tok.getType() == TokenType.minus)
		{
			add_op = new ParseTree("add_op",1);
			add_op.setLeaf(0, match(TokenType.minus));
		}
		else
		{
			throw new ParseException("+,- expected");
		}
		return add_op;
	}
	
	private ParseTree mult_op() throws ParseException
	{
		Token tok = tokens.get(0);
		ParseTree mult_op;
		if( tok.getType() == TokenType.times)
		{
			mult_op = new ParseTree("mult_op",1);
			mult_op.setLeaf(0, match(TokenType.times));
		}
		else if (tok.getType() == TokenType.div)
		{
			mult_op = new ParseTree("mult_op",1);
			mult_op.setLeaf(0, match(TokenType.div));
		}
		else
		{
			throw new ParseException("*,/ Expected");
		}	
		return mult_op;
	}
	
	private Token match(TokenType t)
	{
		if (tokens.get(0).getType() == t)
		{
			//Save the token
			Token tok = tokens.get(0);
			//Consume the token
			tokens.remove(0);
			return tok;
		}
		else return null;
	}
	
	//------------ Public Methods ----------------------------
	public ParseTree getRoot()
	{
		return this.root;
	}
}
